#----------------------------------------------------
# file      : compile_template.cmake
# brief     : 将特定项目的编译代码封装为cmake宏
# author    : wreng
# date      : 2024年8月12日
# version   : 2.0
# copyright : Copyright (c) 2024
#----------------------------------------------------

function(fun_compile_mex_v100)
    #fun_compile_mex_v100 函数功能: 编译mex
    # 
    # mex项目目录结构:
    #   Mex-Proj-Folder
    #   ├─ CMakeLists.txt
    #   ├─ YourTargetName.cpp(mex)
    #   ├─ MfunctionName.m(mex)
    #   └─ ...
    #
    #   说明: 
    #     工程文件夹(Mex-Proj-Folder)下必须包含: 
    #     1. CMakeLists.txt, 配置编译参数并调用本宏完成编译mex流程;
    #     2. YourTargetName.cpp, 由开发者编写的mex接口源文件;
    #     3. m函数文件, 由开发者编写并提供给用户以调用mex.
    #     注: 工程文件夹可以包含其他文件, 按需进行编译参数的配置.
    #
    # 函数的使用:
    #   fun_compile_mex_v100(
    #       TARGET_NAME <YourTargetName>            # 当前mex目标名
    #       MFUNCTION_NAME <YourMfunctionBaseName>  # mex接口m函数名(不带扩展名)
    #       [SRC_FILES] [source-files]              # 除mex-cpp外的其他源文件
    #       [INC_PATH] [include-path]               # 除当前工程文件夹的其他包含路径
    #       [LINK_PATH] [library-path]              # 依赖库路径
    #       [LINK_NAMES] [link-library]             # 依赖库名
    #       [COMP_OPT] [compile-options]            # 编译选项
    #       [MFUN_INSTALL_PATH] [fun-install-path])  # m 函数安装路径
    #       [MEX_INSTALL_PATH] [mex-install-path])   # mex安装路径
    #  
    #   说明: 
    #      1. 必须指定 TARGET_NAME ;
    #      2. TARGET_NAME必须与mex-cpp文件同名, 会根据TARGET_NAME来构造mex-cpp文件路径;
    #
    # 版  本: 1.0.0
    # 作  者: wreng
    # 创建日期: 2024年8月12日
    # 更新日期: 2024年8月12日

    # 解析参数
    set(options "")
    set(oneValueArgs "")
    set(multiValueArgs 
        TARGET_NAME         # 当前mex目标名
        MFUNCTION_NAME      # mex接口m函数名(不带扩展名)
        SRC_FILES           # 除mex-cpp外的其他源文件
        INC_PATH            # 除工程文件夹下的其他包含路径
        LINK_PATH           # 依赖库路径
        LINK_NAMES          # 依赖库名
        COMP_OPT            # 编译选项
        MFUN_INSTALL_PATH   # m文件安装路径
        MEX_INSTALL_PATH    # mex文件安装路径
        )
    cmake_parse_arguments(MACPAR "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    # 参数检查
    if(NOT MACPAR_TARGET_NAME)
        message(FATAL_ERROR "[fun_compile_mex_v100]: TARGET_NAME is empty!")
    endif()
    message(STATUS "[+] MACPAR_TARGET_NAME: ${MACPAR_TARGET_NAME}")
  
    # 检查是否引入 matlab_config
    if (NOT DEFINED MAC_ENABLE_MATLAB)
        message(FATAL_ERROR "cannot find <MAC_ENABLE_MATLAB>!")
    endif()

    # 添加mex-cpp源文件
    set(MEX_CPP_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${MACPAR_TARGET_NAME}.cpp)
    LIST(APPEND MACPAR_SRC_FILES ${MEX_CPP_FILE})

    # 添加mex包含路径
    LIST(APPEND MACPAR_INC_PATH ${MEX_INC_PATH})
    LIST(APPEND MACPAR_INC_PATH ${CMAKE_SOURCE_DIR}/mex/public)

    # 添加mex库路径
    LIST(APPEND MACPAR_LINK_PATH ${MEX_LIB_PATH})

    # 添加mex库
    LIST(APPEND MACPAR_LINK_NAMES ${MEX_LIB})

    # 添加编译选项
    # 个人程序需要, 定义宏USE_MEX给C++编译器进行识别
    LIST(APPEND MACPAR_COMP_OPT -DUSE_MEX)

    # 打印配置信息
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Source Files: ${MACPAR_SRC_FILES}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Include Path: ${MACPAR_INC_PATH}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Library Path: ${MACPAR_LINK_PATH}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Library Name: ${MACPAR_LINK_NAMES}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Compile Opti: ${MACPAR_COMP_OPT}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Install Path(fun): ${MACPAR_MFUN_INSTALL_PATH}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Install Path(mex): ${MACPAR_MEX_INSTALL_PATH}")

    # 编译目标
    add_library(${MACPAR_TARGET_NAME} SHARED ${MACPAR_SRC_FILES})
    target_include_directories(${MACPAR_TARGET_NAME} PRIVATE ${MACPAR_INC_PATH})
    target_link_directories(${MACPAR_TARGET_NAME} PRIVATE ${MACPAR_LINK_PATH})
    target_link_libraries(${MACPAR_TARGET_NAME} PRIVATE ${MACPAR_LINK_NAMES})
    target_compile_options(${MACPAR_TARGET_NAME} PRIVATE ${MACPAR_COMP_OPT})
    # 设置文件名(不要前缀lib)
    set_target_properties(${MACPAR_TARGET_NAME} PROPERTIES PREFIX "") 
    # 修改扩展名
    set_target_properties(${MACPAR_TARGET_NAME} PROPERTIES SUFFIX ".${MEX_EXTENTION}") 

    # 安装 m 函数文件
    if(MACPAR_MFUN_INSTALL_PATH)
        set(M_FUNCTION_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${MACPAR_MFUNCTION_NAME}.m)
        install(FILES ${M_FUNCTION_FILE} DESTINATION ${MACPAR_MFUN_INSTALL_PATH})    
    endif()

    # 安装 mex 文件
    if(MACPAR_MEX_INSTALL_PATH)
        install(FILES ${MEX_RELEASE_BUILD_DIRECTORY}/${MACPAR_TARGET_NAME}.${MEX_EXTENTION} DESTINATION ${MACPAR_MEX_INSTALL_PATH})
    endif()

endfunction()

function(fun_compile_exe_v100)
    #fun_compile_exe_v100 函数功能: 编译可执行程序
    # 
    # 函数的使用:
    #   fun_compile_exe_v100(
    #       TARGET_NAME <YourTargetName>            # 可执行程序名, 不能为空
    #       [SRC_FILES] [source-files]              # 待编译的源文件
    #       [INC_PATH] [include-path]               # 包含路径
    #       [LINK_PATH] [library-path]              # 依赖库路径
    #       [LINK_NAMES] [link-library]             # 依赖库名
    #       [COMP_OPT] [compile-options]            # 编译选项
    #       [INSTALL_PATH] [exe-install-path])      # 可执行程序安装路径
    #  
    #   说明: 必须指定 TARGET_NAME 和 SRC_FILES 且两者不能为空;
    #
    # 版  本: 1.0.0
    # 作  者: wreng
    # 创建日期: 2024年8月12日
    # 更新日期: 2024年8月12日

    # 解析参数
    set(options "")
    set(oneValueArgs "")
    set(multiValueArgs 
        TARGET_NAME     # 可执行程序名
        SRC_FILES       # 源文件
        INC_PATH        # 包含路径
        LINK_PATH       # 依赖库路径
        LINK_NAMES      # 依赖库名
        COMP_OPT        # 编译选项
        INSTALL_PATH    # 安装路径
        )
    cmake_parse_arguments(MACPAR "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    # 参数检查
    if(NOT MACPAR_TARGET_NAME)
        message(FATAL_ERROR "[fun_compile_exe_v100]: TARGET_NAME is empty!")
    endif()
    message(STATUS "[+] MACPAR_TARGET_NAME: ${MACPAR_TARGET_NAME}")

    if(NOT MACPAR_SRC_FILES)
        message(FATAL_ERROR "[fun_compile_mex_v100]: MACPAR_SRC_FILES is empty!")
    endif()

    # 打印配置信息
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Source Files: ${MACPAR_SRC_FILES}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Include Path: ${MACPAR_INC_PATH}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Library Path: ${MACPAR_LINK_PATH}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Library Name: ${MACPAR_LINK_NAMES}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Compile Opti: ${MACPAR_COMP_OPT}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Install Path: ${MACPAR_INSTALL_PATH}")

    # 编译目标
    add_executable(${MACPAR_TARGET_NAME} ${MACPAR_SRC_FILES})
    target_include_directories(${MACPAR_TARGET_NAME} PRIVATE ${MACPAR_INC_PATH})
    target_link_directories(${MACPAR_TARGET_NAME} PRIVATE ${MACPAR_LINK_PATH})
    target_link_libraries(${MACPAR_TARGET_NAME} PRIVATE ${MACPAR_LINK_NAMES})
    target_compile_options(${MACPAR_TARGET_NAME} PRIVATE ${MACPAR_COMP_OPT})

    # 安装
    if (MACPAR_INSTALL_PATH)
        install(TARGETS ${MACPAR_TARGET_NAME} RUNTIME DESTINATION ${MACPAR_INSTALL_PATH})    
    endif()

endfunction()

function(fun_tran2xcompiler_for_cudalib XCOMP_OPT_STR COMP_OPT)
    # 此函数将编译选项列表转换为 "-Xcompiler=/utf8 /bigobj" 的格式
    # 输入: COMP_OPT 是一个 list 变量
    # 输出: 转换后的字符串, 可作为 target_compile_options 的输入
    # BUG 只在windows端需要使用, 但有时貌似不起作用.
        
    set(COMP_OPT_LIST "")
    foreach(ITEM ${${COMP_OPT}})
        LIST(APPEND COMP_OPT_LIST ${ITEM})
    endforeach()

    string(JOIN " " COMP_OPT_STR ${COMP_OPT_LIST})    
    string(CONCAT TMP_XCOMP_OPT_STR "-Xcompiler=${COMP_OPT_STR}")
    set(${XCOMP_OPT_STR} ${TMP_XCOMP_OPT_STR} PARENT_SCOPE)
endfunction()

macro(mac_compile_cudalib_v100)
    #mac_compile_cudalib_v101 宏函数功能: 编译可cuda静态库
    # BUG 写成function会报错: CUDA_ARCHITECTURES is set to "native", but no GPU was detected.
    # 
    # 函数的使用:
    #   mac_compile_cudalib_v100(
    #       TARGET_NAME <YourTargetName>            # 可执行程序名, 不能为空
    #       [SRC_FILES] [source-files]              # 待编译的源文件
    #       [INC_PATH] [include-path]               # 除cuda外的其他包含路径
    #       [LINK_PATH] [library-path]              # 除cuda外的其他依赖库路径
    #       [LINK_NAMES] [link-library]             # 除cuda外的其他依赖库名
    #       [COMP_OPT] [compile-options]            # 编译选项
    #       [INSTALL_PATH] [lib-install-path])      # 静态库安装路径
    #  
    #   说明: 必须指定 TARGET_NAME 和 SRC_FILES 且两者不能为空;
    #
    #   更新:
    #       2024.10.2 适配在LINUX进行编译
    #
    # 版  本: 1.0.1
    # 作  者: wreng
    # 创建日期: 2024年8月15日
    # 更新日期: 2024年10月2日

    # 解析参数
    set(options "")
    set(oneValueArgs "")
    set(multiValueArgs 
        TARGET_NAME     # 静态库名
        SRC_FILES       # 源文件
        INC_PATH        # 除cuda外的其他包含路径
        LINK_PATH       # 除cuda外的其他依赖库路径
        LINK_NAMES      # 除cuda外的其他依赖库名
        COMP_OPT        # 编译选项
        INSTALL_PATH    # 安装路径
        )
    cmake_parse_arguments(MACPAR "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    # 参数检查
    if(NOT MACPAR_TARGET_NAME)
        message(FATAL_ERROR "[mac_compile_cudalib_v100]: TARGET_NAME is empty!")
    endif()
    message(STATUS "[+] MACPAR_TARGET_NAME: ${MACPAR_TARGET_NAME}")

    if(NOT MACPAR_SRC_FILES)
        message(FATAL_ERROR "[mac_compile_cudalib_v100]: MACPAR_SRC_FILES is empty!")
    endif()

    # 启动CUDA
    enable_language(CUDA)

    # 判断是否启动 CUDA
    include(CheckLanguage)
    check_language(CUDA)
    if(CMAKE_CUDA_COMPILER)
        message(STATUS "[+] (${MACPAR_TARGET_NAME}) Find CUDA !")
    else()
        message(FATAL_ERROR "[+] (${MACPAR_TARGET_NAME}) Not found CUDA!")
    endif()

    set(CMAKE_CUDA_ARCHITECTURES ${CUDA_ARCHITECTURES})
    message(STATUS "[+] CMAKE_CUDA_ARCHITECTURES: ${CMAKE_CUDA_ARCHITECTURES}")

    # 添加CUDA编译原文件
    LIST(APPEND MACPAR_SRC_FILES ${CUDA_SRC_FILES})

    # 添加CUDA包含路径
    LIST(APPEND MACPAR_INC_PATH ${CUDA_INC_PATH} )

    # 添加CUDA库路径
    LIST(APPEND MACPAR_LINK_PATH ${CUDA_LIB_PATH})

    # 添加CUDA库
    LIST(APPEND MACPAR_LINK_NAMES ${CUDA_LIB})

    # 添加编译选项
    LIST(APPEND MACPAR_COMP_OPT ${CUDA_COMP_OPT})

    # MSVC需要将编译选项进行转换
    if(MSVC)
        fun_tran2xcompiler_for_cudalib(MACPAR_XCOMP_OPT_STR MACPAR_COMP_OPT)
    endif()

    # 打印配置信息
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Source Files: ${MACPAR_SRC_FILES}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Include Path: ${MACPAR_INC_PATH}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Library Path: ${MACPAR_LINK_PATH}")
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Library Name: ${MACPAR_LINK_NAMES}")
    if(MSVC)
        message(STATUS "[+] (${MACPAR_TARGET_NAME}) Compile Opti: ${MACPAR_XCOMP_OPT_STR}")
    else()
        message(STATUS "[+] (${MACPAR_TARGET_NAME}) Compile Opti: ${MACPAR_COMP_OPT}")
    endif()
    message(STATUS "[+] (${MACPAR_TARGET_NAME}) Install Path: ${MACPAR_INSTALL_PATH}")

    # 编译目标
    add_library(${MACPAR_TARGET_NAME} STATIC ${MACPAR_SRC_FILES})
    # 这里定义为PUBLIC https://blog.csdn.net/qq_41314786/article/details/129970547
    # 当使用此静态库, 可以只链接lib而无需再重复包含路径
    target_include_directories(${MACPAR_TARGET_NAME} PUBLIC ${MACPAR_INC_PATH})
    target_link_directories(${MACPAR_TARGET_NAME} PUBLIC ${MACPAR_LINK_PATH})
    target_link_libraries(${MACPAR_TARGET_NAME} PUBLIC ${MACPAR_LINK_NAMES})
    if(MSVC) 
        target_compile_options(${MACPAR_TARGET_NAME} PUBLIC ${MACPAR_XCOMP_OPT_STR})
    else()
        target_compile_options(${MACPAR_TARGET_NAME} PUBLIC ${MACPAR_COMP_OPT})
    endif()

    # 安装
    if (MACPAR_INSTALL_PATH)
        install(TARGETS ${MACPAR_TARGET_NAME} ARCHIVE DESTINATION ${MACPAR_INSTALL_PATH})    
    endif()

endmacro()

